// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ReflectT.cs" company="Nitro PDF Software">
//   Copyright (c) Nitro PDF Software 2009
// </copyright>
// <summary>
//   Provides strong-typed reflection of the
//   type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

using CSLib.Net.Extensions;


namespace Nitro.PdfCS.Utilities
{
  #region Namespace Imports

  using System;
  using System.Diagnostics.CodeAnalysis;
  using System.Linq.Expressions;
  using System.Reflection;

  #endregion


  /// <summary>
  /// Provides strong-typed reflection of the <typeparamref name="TTarget"/> 
  /// type.
  /// </summary>
  /// <typeparam name="TTarget">
  /// Type to reflect.
  /// </typeparam>
  public static class Reflect<TTarget>
  {
    #region Public Methods

    /// <summary>
    /// Gets the field represented by the lambda expression.
    /// </summary>
    /// <param name="field">
    /// The field.
    /// </param>
    /// <returns>
    /// Returns an instance of <see cref="System.Reflection.FieldInfo"/>.
    /// </returns>
    /// <exception cref="ArgumentException">
    /// Member is not a field.
    /// </exception>
    [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", 
      Justification = "Cannot change parameter type here.")]
    [SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes", 
      Justification = "There is no other way.")]
    [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", 
      Justification = "There is no other way.")]
    public static FieldInfo GetField(Expression<Func<TTarget, object>> field)
    {
      var info = GetMemberInfo(field) as FieldInfo;
      if (info == null)
      {
        throw new ArgumentException("Member is not a field");
      }

      return info;
    }


    /// <summary>
    /// Gets the method represented by the lambda expression.
    /// </summary>
    /// <param name="method">
    /// The method.
    /// </param>
    /// <returns>
    /// Returns an instance of <see cref="System.Reflection.MethodInfo"/>.
    /// </returns>
    /// <exception cref="ArgumentNullException">
    /// The <paramref name="method"/> is <c>null</c>.
    /// </exception>
    /// <exception cref="ArgumentException">
    /// The <paramref name="method"/> is not a lambda expression or it does not represent a method invocation.
    /// </exception>
    [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", 
      Justification = "Cannot change parameter type here.")]
    [SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes", 
      Justification = "There is no other way.")]
    [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", 
      Justification = "There is no other way.")]
    public static MethodInfo GetMethod(Expression<Action<TTarget>> method)
    {
      return GetMethodInfo(method);
    }


    /// <summary>
    /// Gets the method represented by the lambda expression.
    /// </summary>
    /// <typeparam name="T1">
    /// The type of the 1.
    /// </typeparam>
    /// <param name="method">
    /// The method.
    /// </param>
    /// <returns>
    /// Returns an instance of <see cref="System.Reflection.MethodInfo"/>.
    /// </returns>
    /// <exception cref="ArgumentNullException">
    /// The <paramref name="method"/> is <c>null</c>.
    /// </exception>
    /// <exception cref="ArgumentException">
    /// The <paramref name="method"/> is not a lambda expression or it does not represent a method invocation.
    /// </exception>
    [SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes", 
      Justification = "There is no other way.")]
    [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", 
      Justification = "There is no other way.")]
    [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", 
      Justification = "Cannot change parameter type here.")]
    [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", 
      Justification = "Cannot give a more meaningful name here.")]
    public static MethodInfo GetMethod<T1>(Expression<Action<TTarget, T1>> method)
    {
      return GetMethodInfo(method);
    }


    /// <summary>
    /// Gets the method represented by the lambda expression.
    /// </summary>
    /// <typeparam name="T1">
    /// The type of the 1.
    /// </typeparam>
    /// <typeparam name="T2">
    /// The type of the 2.
    /// </typeparam>
    /// <param name="method">
    /// The method.
    /// </param>
    /// <returns>
    /// Returns an instance of <see cref="System.Reflection.MethodInfo"/>.
    /// </returns>
    /// <exception cref="ArgumentNullException">
    /// The <paramref name="method"/> is <c>null</c>.
    /// </exception>
    /// <exception cref="ArgumentException">
    /// The <paramref name="method"/> is not a lambda expression or it does not represent a method invocation.
    /// </exception>
    [SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes", 
      Justification = "There is no other way.")]
    [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", 
      Justification = "There is no other way.")]
    [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", 
      Justification = "Cannot change parameter type here.")]
    [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", 
      Justification = "Cannot give a more meaningful name here.")]
    public static MethodInfo GetMethod<T1, T2>(Expression<Action<TTarget, T1, T2>> method)
    {
      return GetMethodInfo(method);
    }


    /// <summary>
    /// Gets the method represented by the lambda expression.
    /// </summary>
    /// <typeparam name="T1">
    /// The type of the 1.
    /// </typeparam>
    /// <typeparam name="T2">
    /// The type of the 2.
    /// </typeparam>
    /// <typeparam name="T3">
    /// The type of the 3.
    /// </typeparam>
    /// <param name="method">
    /// The method.
    /// </param>
    /// <returns>
    /// Returns an instance of <see cref="System.Reflection.MethodInfo"/>.
    /// </returns>
    /// <exception cref="ArgumentNullException">
    /// The <paramref name="method"/> is <c>null</c>.
    /// </exception>
    /// <exception cref="ArgumentException">
    /// The <paramref name="method"/> is not a lambda expression or it does not represent a method invocation.
    /// </exception>
    [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", 
      Justification = "Cannot change parameter type here.")]
    [SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes", 
      Justification = "There is no other way.")]
    [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", 
      Justification = "There is no other way.")]
    [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", 
      Justification = "Cannot give a more meaningful name here.")]
    public static MethodInfo GetMethod<T1, T2, T3>(Expression<Action<TTarget, T1, T2, T3>> method)
    {
      return GetMethodInfo(method);
    }


    /// <summary>
    /// Get path represented by PropertyInfos.
    /// </summary>
    /// <param name="properties">
    /// PropertyInfos for concatenation.
    /// </param>
    /// <returns>
    /// Returns an instance of <see cref="System.String"/> containing concatenated property names.
    /// </returns>
    [SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes", 
      Justification = "There is no other way.")]
    public static string GetPath(params PropertyInfo[] properties)
    {
      return Reflect.GetPath(properties);
    }


    /// <summary>
    /// Gets the property represented by the lambda expression.
    /// </summary>
    /// <param name="property">
    /// The property.
    /// </param>
    /// <returns>
    /// Returns an instance of <see cref="System.Reflection.PropertyInfo"/>.
    /// </returns>
    /// <exception cref="ArgumentException">
    /// Member is not a property.
    /// </exception>
    [SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes", 
      Justification = "There is no other way.")]
    [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", 
      Justification = "There is no other way.")]
    [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", 
      Justification = "Cannot change parameter type here.")]
    public static PropertyInfo GetProperty(Expression<Func<TTarget, object>> property)
    {
      var info = GetMemberInfo(property) as PropertyInfo;
      if (info == null)
      {
        throw new ArgumentException("Member is not a property");
      }

      return info;
    }


    /// <summary>
    /// Gets the name of the property.
    /// </summary>
    /// <param name="property">
    /// The property.
    /// </param>
    /// <returns>
    /// An instance of <see cref="System.String"/>.
    /// </returns>
    [SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes", 
      Justification = "There is no other way.")]
    [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", 
      Justification = "There is no other way.")]
    [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", 
      Justification = "Cannot change parameter type here.")]
    public static string GetName(Expression<Func<TTarget, object>> property)
    {
      var info = GetMemberInfo(property) as PropertyInfo;
      if (info == null)
      {
        throw new ArgumentException("Member is not a property");
      }

      return info.Name;
    }

    #endregion


    #region Methods

    /// <summary>
    /// Gets the member info.
    /// </summary>
    /// <param name="member">
    /// The member.
    /// </param>
    /// <returns>
    /// Returns an instance of <see cref="System.Reflection.MemberInfo"/>.
    /// </returns>
    /// <exception cref="ArgumentNullException">
    /// <c>member</c> is <c>null</c>.
    /// </exception>
    /// <exception cref="ArgumentException">
    /// Not a lambda expression.
    /// </exception>
    private static MemberInfo GetMemberInfo(Expression member)
    {
      if (member == null)
      {
        throw new ArgumentNullException("member");
      }

      var lambda = member as LambdaExpression;
      if (lambda == null)
      {
        throw new ArgumentException("Not a lambda expression", "member");
      }

      MemberExpression memberExpr = null;

      // The Func<TTarget, object> we use returns an object, so first statement can be either 
      // a cast (if the field/property does not return an object) or the direct member access.
      if (lambda.Body.NodeType == ExpressionType.Convert)
      {
        // The cast is an unary expression, where the operand is the 
        // actual member access expression.
        memberExpr = ((UnaryExpression)lambda.Body).Operand as MemberExpression;
      }
      else if (lambda.Body.NodeType == ExpressionType.MemberAccess)
      {
        memberExpr = lambda.Body as MemberExpression;
      }

      if (memberExpr == null)
      {
        throw new ArgumentException("Not a member access", "member");
      }

      return memberExpr.Member;
    }


    /// <summary>
    /// Gets the method info.
    /// </summary>
    /// <param name="method">
    /// The method.
    /// </param>
    /// <returns>
    /// Returns an instance of <see cref="System.Reflection.MethodInfo"/>.
    /// </returns>
    /// <exception cref="ArgumentNullException">
    /// <c>method</c> is <c>null</c>.
    /// </exception>
    /// <exception cref="ArgumentException">
    /// Not a lambda expression.
    /// </exception>
    /// <exception cref="ArgumentException">
    /// Not a method call.
    /// </exception>
    private static MethodInfo GetMethodInfo(Expression method)
    {
      if (method == null)
      {
        throw new ArgumentNullException("method");
      }

      var lambda = method as LambdaExpression;
      if (lambda == null)
      {
        throw new ArgumentException("Not a lambda expression", "method");
      }

      if (lambda.Body.NodeType != ExpressionType.Call)
      {
        throw new ArgumentException("Not a method call", "method");
      }

      return ((MethodCallExpression)lambda.Body).Method;
    }

    #endregion
  }
}